package io.github.ali.commons.variable;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

import io.github.ali.commons.function.NAFunction;
import io.github.ali.commons.variable.QuartzActionGenerator.ActionJob;
import io.reactivex.BackpressureStrategy;

/**
 * 混合周期性触发和Cron触发
 * </p>
 * 基于Quartz实现,代替UniversalActionGenerator
 * 
 * @author fuli
 * @date 2018年12月5日
 * @version 1.0.0
 */
public final class MixedActionGenerator {
	private static Logger LOGGER = LoggerFactory.getLogger(MixedActionGenerator.class);
	private static final MixedActionGenerator INSTANCE = new MixedActionGenerator();

	private SchedulerFactory schedulerFactory;
	private Scheduler scheduler;
	private Map<String, ActionEntity> actionEntityMap;
	private ThreadLocal<BackpressureStrategy> currentBackpressureStrategy;

	private MixedActionGenerator() {
		schedulerFactory = new StdSchedulerFactory();
		scheduler=null;
		actionEntityMap = new HashMap<>();
		currentBackpressureStrategy = new ThreadLocal<>();
	};
	private synchronized void putSimpleAction(String actionName, long interval, TimeUnit timeUnit, int maxTimes, Date endDatetime,NAFunction action) {
		SimpleTriggerImpl trigger = new SimpleTriggerImpl();
		trigger.setName(actionName);
		trigger.setRepeatInterval(timeUnit.toMillis(interval));
		trigger.setRepeatCount(maxTimes);
		trigger.setStartTime(new Date());
		if(endDatetime!=null) {
			trigger.setEndTime(endDatetime);
		}
		trigger.setMisfireInstruction(Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY);
		schedule(actionName,action, trigger);
	}
	private synchronized void putCronAction(String actionName, String cronExpression,NAFunction action) {
		CronTrigger trigger=TriggerBuilder.newTrigger().withIdentity(actionName)
		.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionIgnoreMisfires())
		.build();
		schedule(actionName,action, trigger);
	}

	private void setBackpressureStrategy(BackpressureStrategy backpressureStrategy){
		currentBackpressureStrategy.set(backpressureStrategy);
	}
	private void schedule(String actionName,NAFunction action, Trigger trigger) {
		try {
			if (scheduler == null || scheduler.isShutdown()) {
				scheduler = schedulerFactory.getScheduler();
				scheduler.start();
				LOGGER.info("MixedActionGenerator create new scheduler and start it for action [{}]!", actionName);
			}
			// 已存在同名任务则覆盖
			ActionEntity exists = actionEntityMap.get(actionName);
			if (exists != null) {
				scheduler.deleteJob(exists.jobKey);
				actionEntityMap.remove(actionName);
				LOGGER.info("MixedActionGenerator load duplicated action [{}]!", actionName);
			} else {
				LOGGER.info("MixedActionGenerator load action [{}]!", actionName);
			}

			BackpressureStrategy backpressureStrategy = currentBackpressureStrategy.get();
			currentBackpressureStrategy.remove();
			ActionEntity actionEntity = new ActionEntity(actionName, action, backpressureStrategy);

			actionEntityMap.put(actionName, actionEntity);
			JobDataMap jm=new JobDataMap();
			jm.put(QuartzActionGenerator.CONTEXT_ACTION,actionEntity.executeAction);
			scheduler.scheduleJob(JobBuilder.newJob(ActionJob.class).withIdentity(actionEntity.jobKey).setJobData(jm).build(), trigger);
		} catch (SchedulerException e) {
			throw new RuntimeException("MixedActionGenerator schedule [" + actionName + "] error!", e);
		}
	}
	private synchronized void removeAction(String actionName,boolean stopIfEmpty) {
		try {
			if (scheduler == null || scheduler.isShutdown()) {
				return;
			}
			ActionEntity actionEntity = actionEntityMap.get(actionName);
			if (actionEntity != null) {
				scheduler.deleteJob(actionEntity.jobKey);
				actionEntityMap.remove(actionName);
				actionEntity.stop();
				LOGGER.info("MixedActionGenerator remove action [{}]!", actionName);
				if (actionEntityMap.isEmpty() && stopIfEmpty) {
					// 没有要执行的Action则关闭调度
					scheduler.shutdown();
					LOGGER.info("MixedActionGenerator shutdown the scheduler after remove action [{}]!",actionName);
				}
			}
		} catch (SchedulerException e) {
			LOGGER.error("MixedActionGenerator removeAction [{}] error!", actionName, e);
		}
	}
	private synchronized void stop() {
		try {
			if (scheduler == null || scheduler.isShutdown()) {
				return;
			}
			scheduler.clear();
			scheduler.shutdown();

			actionEntityMap.values().stream().forEach(ActionEntity::stop);
			actionEntityMap.clear();
		} catch (SchedulerException e) {
			LOGGER.error("MixedActionGenerator stop error!", e);
		}
	}
	private List<JSONObject> getStatus(){
		if(!actionEntityMap.isEmpty()) {
			return actionEntityMap.values().stream().map((e)->e.report()).collect(Collectors.toList());
		}
		return null;
	}
	private class ActionEntity{
		JobKey jobKey;
		long triggerTimes;
		ActionHandler actionHandler;
		NAFunction executeAction;
		private ActionEntity(String actionName,NAFunction action,BackpressureStrategy backpressureStrategy) {
			actionHandler = ActionHandler.makeActionHandler(backpressureStrategy == null ? BackpressureStrategy.LATEST : backpressureStrategy);
			actionHandler.init();
			executeAction = () -> {
				triggerTimes++;
				actionHandler.execute(action);
			};
			jobKey = new JobKey(actionName);
		}
		private void stop(){
			actionHandler.destory();
		}
		JSONObject report() {
			JSONObject status = new JSONObject();
			status.put("actionName", jobKey.getName());
			status.put("triggerTimes", triggerTimes);
			status.put("executeTimes", actionHandler.executeTimes);
			return status;
		}
	}
	/****************** 对外提供的方法 ********************/
	/**
	 * 设置背压策略，目前仅支持：LATEST、BUFFER<br/>
	 * 每次调用loadAction前都需设置，否则会使用默认值LATEST<br/>
	 * 例：MixedActionGenerator.withBackpressureStrategy(BackpressureStrategy.BUFFER).loadAction(actionName,action);<br/>
	 * 仅本次生效，之后如果再次调用loadAction()则会使用默认BackpressureStrategy.LATEST<br/>
	 * @param backpressureStrategy
	 */
	public static void withBackpressureStrategy(BackpressureStrategy backpressureStrategy){
		INSTANCE.setBackpressureStrategy(backpressureStrategy);
	}
	/**
	 * 按指定间隔执行action无限次
	 * @param actionName
	 * @param interval
	 * @param timeUnit
	 * @param action
	 */
	public static void loadAction(String actionName, long interval, TimeUnit timeUnit, NAFunction action) {
		INSTANCE.putSimpleAction(actionName, interval, timeUnit, -1, null, action);
	}
	/**
	 * 按指定间隔执行action最多maxTimes次
	 * @param actionName
	 * @param interval
	 * @param timeUnit
	 * @param maxTimes < 0则无限次
	 * @param action
	 */
	public static void loadAction(String actionName, long interval, TimeUnit timeUnit,int maxTimes, NAFunction action) {
		INSTANCE.putSimpleAction(actionName, interval, timeUnit, maxTimes < 1 ? -1 : maxTimes, null, action);
	}
	/**
	 * 装载要执行的Action
	 * </p>
	 * 相同的actionName会被覆盖
	 * 
	 * @param actionName
	 * @param interval
	 * @param timeUnit
	 * @param maxTimes
	 * @param endDatetime
	 * @param action 不要抛异常
	 */
	public static void loadAction(String actionName, long interval, TimeUnit timeUnit, int maxTimes, Date endDatetime,NAFunction action) {
		INSTANCE.putSimpleAction(actionName, interval, timeUnit, maxTimes < 1 ? -1 : maxTimes, endDatetime, action);
	}
	/**
	 * 每秒执行action无限次
	 * @param actionName
	 * @param action
	 */
	public static void loadAction(String actionName, NAFunction action) {
		INSTANCE.putCronAction(actionName, CronActionGenerator.DEFAULT_CRON_EXPRESSION, action);
	}
	/**
	 * 按Cron执行action
	 * @param actionName
	 * @param cronExpression
	 * @param action
	 */
	public static void loadAction(String actionName, String cronExpression, NAFunction action) {
		INSTANCE.putCronAction(actionName, cronExpression, action);
	}
	
	/**
	 * 卸载指定Action
	 * 
	 * @param actionName
	 */
	public static void unloadAction(String actionName) {
		INSTANCE.removeAction(actionName, true);
	}
	public static void unloadAction(String actionName,boolean stopIfEmpty) {
		INSTANCE.removeAction(actionName,stopIfEmpty);
	}
	/**
	 * 服务停止时调用此方法
	 */
	public static void shutdown() {
		INSTANCE.stop();
	}
	public static void showStatus() {
		List<JSONObject> actionStatus = INSTANCE.getStatus();
		if (actionStatus != null) {
			LOGGER.info("MixedActionGenerator Action report begin--->");
			actionStatus.stream().forEach((r) -> LOGGER.info(r.toJSONString()));
			LOGGER.info("<---MixedActionGenerator Action report end");
		}
	}
}
